package sa4

import javax.servlet.http._
import scala.xml._
import scala.collection.JavaConversions._
import scala.util.matching.Regex
import javax.servlet.ServletConfig

// This is a very simple Scala servlet.
// If you rename this class, you must also change the file
// src/main/webapp/WEB-INF/web.xml to use the new name.
class SimpleServletWithForms extends HttpServlet {
  type HandlerFunction = (HttpServletRequest, HttpServletResponse) => Unit
  
  override def init(config: ServletConfig) {
    super.init(config)

    routeTable("/") = makeForm _
  }

  override def service(req: HttpServletRequest, resp: HttpServletResponse) {
    if (tableDispatch(req, resp))
      return
    resp.sendError(HttpServletResponse.SC_NOT_FOUND)
  }

  def makeForm(req: HttpServletRequest, resp: HttpServletResponse) {
    val xml = renderPage
    val html = prepareForm(xml)
    resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
  }

  import scala.collection.mutable.HashMap
  val routeTable = new HashMap[String, HandlerFunction]

  def tableDispatch(req: HttpServletRequest, resp: HttpServletResponse): Boolean = {
    routeTable.get(req.getRequestURI) match {
      case Some(f) => f(req, resp); true
      case None => false
    }
  }

  // TODO: Cache the transformed template so we don't have to process each time the page is loaded.
  def prepareForm(template: NodeSeq): NodeSeq = {
    // Ugh. XML transformations in Scala are horrendous.
    import scala.xml.transform._
    import scala.xml.Null // hide scala.Null
    object MakeForm extends RewriteRule {
      override def transform(n: Node): Seq[Node] = n match {
        case form: Elem if form.label == "form" =>
          val Some(handler) = form.attribute("handler")
          
          // Make the handler and install in routeTable
          val (uri, f) = makeHandler(handler.text)
          routeTable(uri) = f
          
          // Add an action to the form.
          form % Attribute(None, "action", Text(uri), Null)
        case n => n
      }
    }

    template.map(ns => new RuleTransformer(MakeForm)(ns))
  }

  def makeHandler(s: String): (String, HandlerFunction) = {
    // Parse the handler string.
    val q = "^(\\w+)\\((.*)\\)$".r

    val (methodName, argNames) = s match {
      case q(methodName, argNames) => (methodName, argNames.split(",").map(_.trim))
      case _ => throw new RuntimeException("bad handler: " + s)
    }

    // Look for a method that takes the right number of strings.
    val method = this.getClass.getMethod(methodName, argNames.map(_ => classOf[String]): _*)

    // TODO: Find methods that take other parameter types and generate conversions.
    // TODO: Generate form validation code.
    // TODO: allow handlers in different classes -- this complicates handler string
    // parsing and also dispatch

    val h = (req: HttpServletRequest, resp: HttpServletResponse) => {
      val result = method.invoke(this, argNames.map(k => req.getParameter(k)): _*)
      result match {
        case s: String =>
          resp.getWriter.write(s)
        case html: NodeSeq =>
          resp.getWriter.write(new PrettyPrinter(72, 2).formatNodes(html))
      }
    }
    
    (handlerURI(methodName, argNames), h)
  }
  
  // Translate a handler string into a URI
  def handlerURI(methodName: String, argNames: Seq[String]) = {
    "/" + methodName // + "_" + argNames.mkString("_")
  }

  def renderPage = {
    <html>
      <head>
        <title>Form example</title>
      </head>
      <body>
        <h3>Post a comment</h3>
        <form method="post" handler="postComment(author, content)">
          <p>
            <span>Your name: </span>
            <input type="text" name="author"/>
          </p>
          <p>
            <span>Your message: </span>
            <textarea name="content"></textarea>
          </p>
          <p>
            <input type="submit" value="Submit your comment"/>
          </p>
        </form>
      </body>
    </html>
  }

  def postComment(author: String, content: String) =
    <html>
      <head>
        <title>Form submitted</title>
      </head>
      <body>
        <h3>Form submitted</h3>
        <p>{ author } submitted the following comment:</p>
        <blockquote>{ content }</blockquote>
      </body>
    </html>
}
